package pl.smath.expression.parser.mathml;

import pl.smath.expression.model.AbstractMultiTerm;
import pl.smath.expression.model.DivisionTerm;
import pl.smath.expression.model.ExponentiationTerm;
import pl.smath.expression.model.FractionTerm;
import pl.smath.expression.model.FunctionTerm;
import pl.smath.expression.model.NumberTerm;
import pl.smath.expression.model.ProductTerm;
import pl.smath.expression.model.RelationTerm;
import pl.smath.expression.model.RootTerm;
import pl.smath.expression.model.SetTerm;
import pl.smath.expression.model.SumTerm;
import pl.smath.expression.model.Term;
import pl.smath.expression.model.VariableTerm;
import pl.smath.expression.model.presentation.AbsoluteTerm;
import pl.smath.expression.model.presentation.AtomTerm;
import pl.smath.expression.model.presentation.ConjugateTerm;
import pl.smath.expression.model.presentation.ExtendedRelationTerm;
import pl.smath.expression.model.presentation.FactorialTerm;
import pl.smath.expression.model.presentation.FactorofTerm;
import pl.smath.expression.model.presentation.FunctionMappingTerm;
import pl.smath.expression.model.presentation.GapTerm;
import pl.smath.expression.model.presentation.IntervalTerm;
import pl.smath.expression.model.presentation.MixedFractionTerm;
import pl.smath.expression.model.presentation.StringTerm;
import pl.smath.expression.model.presentation.SubscriptTerm;
import pl.smath.expression.parser.ExpressionParserException;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.XMLParser;
import com.google.gwt.xml.client.impl.DOMParseException;

public class MathMLParser {

	/**
	 * Parse given expression
	 * @return Root node
	 * @throws ExpressionParserException 
	 */
	public Term parse(String exp) throws ExpressionParserException, DOMParseException{
		Document dom = XMLParser.parse(exp);
		return parseElement(dom.getDocumentElement());
	}
	
	public Term process(Element element) throws ExpressionParserException{
		return parseElement(element);
	}
	
	private Term parseElement(Element element) throws ExpressionParserException{
		
		String nodeName = element.getNodeName().toLowerCase();
		
		try {
		
			if (nodeName.equals("math")){
				return parseElement(XmlUtils.getChildElementNodeAtIndex(0, element));
			} else if (nodeName.equals("apply")){
					
				String name = ((Node)XmlUtils.getChildElementNodeAtIndex(0, element)).getNodeName();
				Term t1 = null;
				
				if (name.equals("plus")){
					t1 = new SumTerm();
				} else if (name.equals("times")){
					t1 = new ProductTerm();
				} else if (name.equals("divide")){
					t1 = new DivisionTerm();
				} else if (name.equals("sin")  ||  name.equals("cos")  ||  name.equals("tg")  ||  name.equals("ctg")  ||
						name.equals("arcsin")  ||  name.equals("arccos")  ||  name.equals("arctg")  ||  name.equals("arcctg")  ||
						name.equals("ln")  ||  name.equals("log")){
					Term t2 = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					return new FunctionTerm(name, t2);
				} else if (name.equals("factorial")){
					Term t2 = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					return new FactorialTerm(t2); 
				} else if (name.equals("power")){
					Term base = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term exp = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					return new ExponentiationTerm(base, exp);
				} else if (name.equals("root")){
					Term degree = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term base = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					return new RootTerm(base, degree);
				} else if (name.equals("abs")){
					return new AbsoluteTerm( parseElement(XmlUtils.getChildElementNodeAtIndex(1, element)) );
				} else if (name.equals("conjugate")){
					return new ConjugateTerm( parseElement(XmlUtils.getChildElementNodeAtIndex(1, element)) );
				} else if (name.equals("fraction")){
					Term num = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term denom = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					return new MixedFractionTerm(num, denom);
				} else if (name.equals("eq")  ||  name.equals("neq")  ||  
						name.equals("geq")  ||  name.equals("leq")  ||  name.equals("lt")  ||  name.equals("gt")){
					Term left = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term right = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					return new RelationTerm(relationNodeNameToSymbol(name), left, right);
				} else if (name.equals("equivalent")  ||  name.equals("approx")  ||  
						name.equals("isomorphic")  ||  name.equals("orthogonal")  ||  
						name.equals("parallel")  ||  name.equals("identical")){
					Term left = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term right = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					return new ExtendedRelationTerm(relationNodeNameToSymbol(name), left, right);		
				} else if (name.equals("factorof")  ||  name.equals("notfactorof")){
					Term left = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term right = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					return new FactorofTerm(name, left, right);
				} else if (name.equals("functionmap")){
					Term function  = parseElement(XmlUtils.getChildElementNodeAtIndex(1, element));
					Term from = parseElement(XmlUtils.getChildElementNodeAtIndex(2, element));
					Term to = parseElement(XmlUtils.getChildElementNodeAtIndex(3, element));
					return new FunctionMappingTerm(function, from, to);
				}
					
				if (t1 instanceof AbstractMultiTerm){
					for (int i = 1 ; i < element.getChildNodes().getLength() ; i ++){
						Term t2 = parseElement((Element)element.getChildNodes().item(i));
						((AbstractMultiTerm)t1).addTerm(t2);
					}
				}	
				
				return t1;
				
			} else if (nodeName.equals("cn")){
				return new NumberTerm(XmlUtils.getFirstTextNode(element).toString());
			} else if (nodeName.equals("cs")){
				if (!XmlUtils.hasChildElementNodes(element)){
					if (element.getChildNodes().getLength() > 0)
						return new StringTerm(element.getFirstChild().toString());
					else
						return new StringTerm(" ");
				} else {
					StringTerm st = new StringTerm();
					for (int n = 0 ; n < element.getChildNodes().getLength() ; n ++ ){
						if (element.getChildNodes().item(n).getNodeType() == Node.ELEMENT_NODE){
							st.addTerm(parseElement((Element)element.getChildNodes().item(n)));
						}
					}
					return st;
				}
			} else if (nodeName.equals("ci")){
				if (element.getFirstChild().getNodeType() == Node.TEXT_NODE)
					return new VariableTerm(element.getFirstChild().toString());
				else if (element.getChildNodes().getLength() == 1)
					return parseElement(XmlUtils.getChildElementNodeAtIndex(0, element));
			} else if (nodeName.equals("msup")){
				Term base = parseElement( XmlUtils.getChildElementNodeAtIndex(0, element) );
				Term sup = parseElement( XmlUtils.getChildElementNodeAtIndex(1, element) );
				return new ExponentiationTerm(base, sup);
			} else if (nodeName.equals("msub")){
				Term base = parseElement( XmlUtils.getChildElementNodeAtIndex(0, element) );
				Term sub = parseElement( XmlUtils.getChildElementNodeAtIndex(1, element) );
				return new SubscriptTerm(base, sub);
			} else if (nodeName.equals("msubsup")){
				Term base = parseElement( XmlUtils.getChildElementNodeAtIndex(0, element) );
				Term sub = parseElement( XmlUtils.getChildElementNodeAtIndex(1, element) );
				Term sup = parseElement( XmlUtils.getChildElementNodeAtIndex(2, element) );
				return new AtomTerm(base, sub, sup);
			} else if (nodeName.equals("interval")){
				Term first = parseElement( XmlUtils.getChildElementNodeAtIndex(0, element) );
				Term second = parseElement( XmlUtils.getChildElementNodeAtIndex(1, element) );
				String closureTypeString = "";
				if (element.hasAttribute("closure"))
					closureTypeString = element.getAttribute("closure");
				return new IntervalTerm(closureTypeString, first, second);
			} else if (nodeName.equals("gap")){
				return new GapTerm();
			} else if (nodeName.equals("infinity")){
				return new StringTerm(String.valueOf((char)8734));
			} else if (nodeName.equals("triangle")){
				return new StringTerm(String.valueOf((char)9651));
			} else if (nodeName.equals("angle")){
				return new StringTerm(String.valueOf((char)8736));
			} else if (nodeName.equals("set")){
				int count = XmlUtils.getChildElementNodesCount(element);
				SetTerm setTerm = new SetTerm();
				for (int n = 0 ; n < count ; n ++){
					setTerm.addTerm(parseElement(XmlUtils.getChildElementNodeAtIndex(n, element)));
				}
				return setTerm;
			}
			
		} catch (Exception e) {
			throw new ExpressionParserException("Failed to parse: " + element.toString(), null);
		}
		
		throw new ExpressionParserException("Failed to parse: " + element.toString(), null);
		
	}
	
	
	private String relationNodeNameToSymbol(String name){
		if (name.equals("eq")){
			return "=";
		} else if (name.equals("neq")){
			return "!=";
		} else if (name.equals("geq")){
			return ">=";
		} else if (name.equals("leq")){
			return "<=";
		} else if (name.equals("gt")){
			return ">";
		} else if (name.equals("lt")){
			return "<";
		} else if (name.equals("identical")){
			return "===";
		} else if (name.equals("equivalent")){
			return "~";
		} else if (name.equals("approx")){
			return "~=";			
		} else if (name.equals("isomorphic")){
			return "~==";			
		} else if (name.equals("orthogonal")){
			return "_|_";			
		} else if (name.equals("parallel")){
			return "||";			
		}
		return "";
	}
}
